package org.friping.mytool.helper;

import android.content.ContentValues;
import android.database.Cursor;

import com.fasterxml.jackson.databind.ObjectMapper;

import org.androidannotations.annotations.AfterInject;
import org.androidannotations.annotations.EBean;
import org.friping.mytool.entity.BaseEntity;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toList;

@EBean
public class EntityConverter {

    private ObjectMapper mapper = new ObjectMapper();
    private DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @AfterInject
    void init(){
        mapper.setDateFormat(df);
    }

    public <T extends BaseEntity> T mapToEntity(T entity, Map m){
        Map entityMap = entityToMap(entity);
        entityMap.putAll(m);
        return mapper.convertValue(entityMap, (Class<T>) entity.getClass());
    }

    public <T extends BaseEntity> T mapToEntity(Class<T> entityClass, Map m){
        T entity = mapper.convertValue(m, entityClass);
        return entity;
    }

    public Map entityToMap(BaseEntity entity){
        return mapper.convertValue(entity, Map.class);
    }

    public ContentValues entityToContentValues(BaseEntity entity){
        Map m = mapper.convertValue(entity, Map.class);
        ContentValues contentValues = mapToContentValues(m);
        return contentValues;
    }

    public ContentValues mapToContentValues(Map m){
        ContentValues contentValues = new ContentValues();
        for(Iterator iterator = m.keySet().iterator(); iterator.hasNext();){
            String key = (String) iterator.next();
            Object value = m.get(key);
            if(value == null){
                contentValues.putNull(key);
            }else if(value instanceof Long){
                contentValues.put(key, (Long) m.get(key));
            }else if(value instanceof Byte){
                contentValues.put(key, (Byte) m.get(key));
            }else if(value instanceof Integer){
                contentValues.put(key, (Integer) m.get(key));
            }else if(value instanceof Short){
                contentValues.put(key, (Short) m.get(key));
            }else if(value instanceof Boolean){
                contentValues.put(key, (Boolean) m.get(key));
            }else if(value instanceof Float){
                contentValues.put(key, (Float) m.get(key));
            }else if(value instanceof Float){
                contentValues.put(key, (Float) m.get(key));
            }else if(value instanceof byte[]){
                contentValues.put(key, (byte[]) m.get(key));
            }else if(value instanceof Date){
                contentValues.put(key, df.format(value));
            }else if(value instanceof String){
                contentValues.put(key, (String) value);
            }
        }
        return contentValues;
    }

    public Map contentValuesToMap(ContentValues contentValues){
        Map m = new HashMap();
        for(Iterator iterator = contentValues.keySet().iterator(); iterator.hasNext();){
            String key = (String) iterator.next();
            Object value = contentValues.get(key);
            m.put(key, value);
        }
        return m;
    }

    public BaseEntity cursorToEntity(Cursor c, Class<? extends BaseEntity> entityClass){
        try {
            return (BaseEntity) setValues2Fields(c, entityClass);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public Map cursorToMap(Cursor c, Class<? extends BaseEntity> entityClass){
        try {
            return entityToMap((BaseEntity) setValues2Fields(c, entityClass));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }




    /* --------- cursor to entity --------- */
    /**
     * 把值设置进类属性里
     */
    private Object setValues2Fields(Cursor c, Class clazz)
            throws Exception {
        String[] columnNames = c.getColumnNames();// 字段数组
        Object obj = clazz.newInstance();
        Field[] fields = getFields(clazz);

        for (Field _field : fields) {
            Class<? extends Object> typeClass = _field.getType();// 属性类型
            for (int j = 0; j < columnNames.length; j++) {
                String columnName = columnNames[j];
                typeClass = getBasicClass(typeClass);
                boolean isBasicType = isBasicType(typeClass);

                if (isBasicType) {
                    if (columnName.equalsIgnoreCase(_field.getName())) {// 是基本类型
                        String _str = c.getString(c.getColumnIndex(columnName));
                        if (_str == null) {
                            break;
                        }
                        // for boolean
                        if(Boolean.class.isAssignableFrom(typeClass)){
                            _str = _str.equals("1") ? "true" : "false";
                        }
                        _str = _str == null ? "" : _str;
                        Constructor<? extends Object> cons = typeClass
                                .getConstructor(String.class);
                        Object attribute = cons.newInstance(_str);
                        _field.setAccessible(true);
                        _field.set(obj, attribute);
                        break;
                    }
                } else if(Date.class.isAssignableFrom(typeClass)) {
                    if (columnName.equalsIgnoreCase(_field.getName())) {// date
                        String _str = c.getString(c.getColumnIndex(columnName));
                        _field.setAccessible(true);
                        if(_str != null){
                            _field.set(obj, df.parse(_str));
                        }
                        break;
                    }
                } else {
                    Object obj2 = setValues2Fields(c, typeClass);// 递归
                    _field.set(obj, obj2);
                    break;
                }

            }
        }
        return obj;
    }

    private Field[] getFields(Class clazz) {
        List<Field> fieldsList = new ArrayList();
        if(BaseEntity.class.isAssignableFrom(clazz)){
            fieldsList.addAll(Arrays.asList(getFields(clazz.getSuperclass())));
        }
        Field[] fields = clazz.getDeclaredFields();
        fieldsList.addAll(Stream.of(fields).filter(field -> {
            // Only private field
            if(field.getModifiers() == Modifier.PRIVATE){
                return true;
            }
            return false;
        }).collect(toList()));
        return fieldsList.toArray(new Field[0]);
    }

    /**
     * 判断是不是基本类型
     *
     * @param typeClass
     * @return
     */
    @SuppressWarnings("rawtypes")
    private boolean isBasicType(Class typeClass) {
        if (typeClass.equals(Integer.class) || typeClass.equals(Long.class)
                || typeClass.equals(Float.class)
                || typeClass.equals(Double.class)
                || typeClass.equals(Boolean.class)
                || typeClass.equals(Byte.class)
                || typeClass.equals(Short.class)
                || typeClass.equals(String.class)) {

            return true;

        } else {
            return false;
        }
    }

    /**
     * 获得包装类
     *
     * @param typeClass
     * @return
     */
    @SuppressWarnings("all")
    private Class<? extends Object> getBasicClass(Class typeClass) {
        Class _class = basicMap.get(typeClass);
        if (_class == null)
            _class = typeClass;
        return _class;
    }

    @SuppressWarnings("rawtypes")
    private Map<Class, Class> basicMap = new HashMap<Class, Class>();
    {
        basicMap.put(int.class, Integer.class);
        basicMap.put(long.class, Long.class);
        basicMap.put(float.class, Float.class);
        basicMap.put(double.class, Double.class);
        basicMap.put(boolean.class, Boolean.class);
        basicMap.put(byte.class, Byte.class);
        basicMap.put(short.class, Short.class);
    }
    /* --------- /cursor to entity --------- */
}
